package de.fhwf.mixedreality.client2007;

import java.awt.Color;

import de.fhwf.mixedreality.client.AgentView;
import de.fhwf.mixedreality.client.AgentViewEx;
import de.fhwf.mixedreality.client.IClient;
import de.fhwf.mixedreality.client.IMovements;
import de.fhwf.mixedreality.client.IPositionsStorage;
import de.fhwf.mixedreality.client.IView;
import de.fhwf.mixedreality.client.UDPConnection;

public class Client2007 implements IClient {
 private UDPConnection mConnection; 
 private MessageParser mParser;
 private CommandInterface mCmd;
 private Movements mMovements;
 private PositionsStorage mPositions;
 private long mLastFlush;
 private IView mView;
 private String mServerAddress;
 private int mServerPort;
 private String mBotName = "";
 private String mTeamName = "";
 private long mByteCount = 0;
 
 /**
  * This function initializes the client,
  * connecting it to the server. It returns
  * false on fail or true on success
  */
 public Client2007(String serverAddress, Team team) {
	int serverPort = 0;
	if (team == Team.BLUE) {
	 serverPort = 3200;
	} else {
 	 serverPort = 3100;
	}

  mServerAddress = serverAddress;
  mServerPort = serverPort;
  mPositions = new PositionsStorage(this);
  mCmd = new CommandInterface();
  mParser = new MessageParser(mPositions);
  mMovements = new Movements(mPositions, mCmd);
  mConnection = new UDPConnection(serverAddress, serverPort);
 }
 
 /**
  * Same as Client(String, int), but additionally auto-updates
  * the given view.
  */
 public Client2007(String serverAddress, Team team, AgentView view) {
  this(serverAddress, team);
  mView = view;
 }

 /**
  * Same as Client(String, int, AgentView), but additionally sets the
  * bot name.
  */
 public Client2007(String serverAddress, Team team, AgentView view, String name) {
  this(serverAddress, team, view);
  mBotName = name;
 }
 
 /**
  * This function should be called on the beginning
  * of every cycle of the main loop. It receives
  * information from the server and updates the
  * situation of known objects around the field.
  * This function returns -1 if the server stopped.
  */
 public boolean update() {
  // Try to receive for about one second
  String recvData = mConnection.receive();
//  LOG.debug("recData: "+recvData);
  return update(recvData);  
 }

 public boolean update(String serverData) {
  // If we have data from the server
  if (!serverData.equals("")) {
   mByteCount += serverData.length() + 2;
   // Parse the received data
//   System.out.println("[Client] update: Received data: " + serverData);
   //long start = System.nanoTime();
   mParser.parse(serverData);
   //long end = System.nanoTime();
   //System.out.println("[Client] update: Parsing took " + (end-start) + "ns");
   if (mView != null) {
    mView.drawClientWorld(this);
   }
   // Return true on success
   return true;
  } else {
   // Return false if there was no message there anymore. This most likely means the connection is lost.
   return false;
  }
 }

 /**
  * This function should be called in the end of
  * every cycle of the main loop. It takes the
  * desired speeds or raw commands and send them
  * to the server for making the robot actually
  * move.
  */
 public void flush() {
  // Get the computed command
  String cmd = mCmd.getCmd();
  // if a view is attached, update it
  if (mView != null) {
   if (cmd.length() > 0) {
	   if (cmd.equals("FNFN") || cmd.equals("BNBN")) {
		   mView.drawText(10, 20, "Command: " + cmd, Color.RED);
	   } else {
		   mView.drawText(10, 20, "Command: " + cmd);
	   }
   }
   mView.update(this);
  }
  // Checks whether the command is empty or not
  if (cmd.length() > 0) {
   mByteCount += cmd.length() + 2;
   // Send the command data to the server using the UDP connection
   mConnection.send(cmd);
  }
  // Let's have a delay here because keeping the server busy helps nobody
  int dur = 0;
  if (mLastFlush != 0) {
   dur = (int)((System.nanoTime() - mLastFlush) / 1000000);
  }
  int sleep = 30 - dur;
  if (sleep > 0) {
   //System.out.println("[Client] flush: Last iteration took " + dur + "ms, waiting " + sleep + "ms");
   try {
    Thread.sleep(sleep); // 15fps
   } catch (InterruptedException e) {
   }
  } else {
   //System.out.println("[Client] flush: Last iteration took " + dur + "ms, not waiting");
  }
  mLastFlush = System.nanoTime();
 }

 /**
  * This function disconnects the client and
  * should be called before finishing the program
  */
 public void close(){
  mConnection.close();
 // System.out.println("[Client] close: Bytes transferred during game: " + mByteCount + " (" + (mByteCount / 1024) + "kb)");
 }

 /** Returns the object which is used to issue commands to the bot */
 public IMovements getMovements() {
  return mMovements;
 }

 /** Returns the object which is used to issue commands to the bot */
 public IMovements Movements() {
  return mMovements;
 }

 /** 
  * Returns the object which contains the current positions 
  * of all elements in the world and calculations functions 
  * for them
  */
 public IPositionsStorage getPositions() {
  return mPositions;
 }

 /** 
  * Returns the object which contains the current positions 
  * of all elements in the world and calculations functions 
  * for them
  */
 public IPositionsStorage Positions() {
  return mPositions;
 }

 /** Returns the time of last update in the server clock, in seconds */
 public double time() {
  return mParser.getTime();
 }
 
 /** Returns true if the game is on or false if not */
 public boolean isGameOn() {
  return mParser.isGameOn();
 }
 
 /** Return the id (a.k.a. t-shirt number) of one's self */
 public int myID() {
  return mParser.getMyID();
 }

 /**
  * Tells whether you are lost or not. Being lost
  * happens when the robot is removed from the field
  * or if the camera cannot find it for any reason.
  */
 public boolean iAmLost() {
  return mParser.isLost();
 }
 
 /**
  * Tells if a given opponent was found in the field
  * @param  id  The ID of the opponent to look for
  */
 public boolean isOpponentFound(int id) {
  return mParser.getOpponentUpdateState(id);
 }

 /**
  * Tells if a given teammate was found in the field
  * @param  id  The ID of the teammate to look for
  */
 public boolean isTeammateFound(int id) {
  if (id != myID()) {
   return mParser.getTeammateUpdateState(id);
  } else {
   return false;
  }
 }
 
 public int goalsTeam() {
  return mParser.getTeamGoals();
 }

 public int goalsOpponents() {
  return mParser.getOpponentGoals();
 }

 public GameMode getGameMode() {
	 if (mParser.isGameOn()) {
		 return GameMode.PLAY_ON;
	 } else {
		 return GameMode.FROZEN;
	 }
 }

 public IView getView() {
  return mView;
 }
 
 public void setView(IView newView) {
  mView = newView;
 }

 public boolean isSimulated() {
  return (mView instanceof AgentViewEx);
 }
 
 public String peekCmd() {
  String cmd = mCmd.getCmd();
  mCmd.setRaw(cmd);
  return cmd;
 }
 
 public String getLastCmd() {
  return mCmd.getLastCmd();
 }

 public String getServerAddress(){
	 return this.mServerAddress;
 }
 
 public int getServerPort(){
	 return this.mServerPort;
 }
 
 public void setBotName(String name) {
	 mBotName = name;
 }
 
 public String getBotName() {
	 return mBotName;
 }

 public String getTeamName() {
	 return mTeamName;
 }
	
 public void setTeamName(String name) {
	 mTeamName = name;
 }

 public void setRawCommand(String cmd) {
	mCmd.setRaw(cmd); 
 }

 public boolean connect() {
  System.out.println("[Client] Connecting to server...");
	// Connect to the game server by sending a "hello" message
	mConnection.send("hello");
	  
	// wait some time to increase the chances that the server already registered us before we start playing
	try {
	 Thread.sleep(1000);
	} catch (InterruptedException e) {
	 e.printStackTrace();
	}
	
	return true;
 }
}